/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.google.code.twiddling.core.io.text;

import java.util.ArrayList;

/**
 * This interface represents a table output.
 * 
 * A Table Contains:
 * 
 * A Title --> short description of the table. 
 *   attributes: h-align(left, right, center, fill)
 *               decoration(underline)
 * 
 * A Subtitle --> the short description below the title.
 *   attributes: h-align
 *               decoration (underline)
 * 
 * Table Body --> the content part of the table
 *   attributes: h-align(....) -- position of whole table relative to the terminal width.
 *               column separator (|, space, etc)
 *               row separator (none, space, -, etc)
 * 
 * Column Definition --> Definition for each column.
 *   attributes: order -- indicating the relative position of the column to others.
 *               name -- name for the column
 *               header alignment -- alignment for the headers
 *               h-align -- horizontal alignment of the column data
 *               width -- in chars, width of the column
 *               v-align (top, bottom, center) -- vertical alignment of the column data
 * 
 * @author <a href="mailto:howard.gao@gmail.com">Howard Gao</a> 
 *
 */
public class TableSchema implements OutputSchema {

	private String name;
	private TitleFormat titleFormat; //stores attrs for table title and sub-title
	private ArrayList<ColumnFormat> headerFormat = new ArrayList<ColumnFormat>(); //stores attrs of columns
	private boolean showHeaders = true;
	private String colSep = "|";
	
	public TableSchema(String schemaName) {
		name = schemaName;
		titleFormat = new TitleFormat();
	}
	
	public String getName() {
		return name;
	}
	
	public TextTable newTable() {
		return new TextTable(this);
	}

	public void formatTitles(ArrayList<String> allLines, TextTable table) {
		titleFormat.format(allLines, table);
	}

	public void formatBody(ArrayList<String> allLines, TextTable table) {
		ArrayList<ArrayList<Cell>> rows = table.getRows();
		CellFormat[] cellFmts = new CellFormat[headerFormat.size()];
		for (int i = 0; i < cellFmts.length; i++) {
			ColumnFormat colFmt = headerFormat.get(i);
			cellFmts[i] = colFmt.getCellFormat();
		}
		for (ArrayList<Cell> row : rows) {
			formatRow(allLines, row.toArray(new Cell[0]), cellFmts, table);
		}
	}

	public void formatHeader(ArrayList<String> allLines, TextTable table) {
		this.calculateColWidths(table);
		if (showHeaders) {
			ArrayList<Cell> cols = getColumns();
	        formatRow(allLines, cols.toArray(new Cell[0]), headerFormat.toArray(new CellFormat[0]), table);
			allLines.add(table.getHLine());
		}
	}
	
	private ArrayList<Cell> getColumns() {
		ArrayList<Cell> result = new ArrayList<Cell>();
		for (ColumnFormat cf : headerFormat) {
			result.add(cf.getCell());
		}
		return result;
	}
	
	private void calculateColWidths(TextTable table) {
		int wid = table.getWidth();
        int avgWid;
        if ( headerFormat.size() == 0 ) {
        	avgWid = wid;
        } else {
        	avgWid = wid / headerFormat.size();
        }
        int totWid = 0;
        for (ColumnFormat col : headerFormat) {
        	if (col.getWidth() == 0) {
        		col.setWidth(avgWid);
        	}
        	totWid += col.getWidth();
        }
        if (totWid >= wid) {
        	//broaden the table
        	table.setWidth(totWid);
        } else {
        	ColumnFormat lastCol = headerFormat.get(headerFormat.size() - 1);
        	lastCol.setWidth(lastCol.getWidth() + (wid - totWid));
        }
	}
	
	/**
	 * Each cell has its own format. Practically the cell's format is subject to 
	 * its column's format.
	 * @param allLines 
	 * @param cells
	 * @param objects 
	 */
	public void formatRow(ArrayList<String> allLines, Cell[] cells, CellFormat[] formats, TextTable table) {
		ArrayList<ArrayList<String>> allCells = new ArrayList<ArrayList<String>>();
		int maxln = 0;
		for (int i = 0; i < cells.length; i++) {
			ArrayList<String> fcell = new ArrayList<String>();
			String cont = cells[i].getStringValue();
			int width = formats[i].getWidth();
			int len = cont.length();
			int nln = 0;
			while (cont.length() > 0) {
				if (len > width) {
					String ln = cont.substring(0, width);
					fcell.add(ln);
					cont = cont.substring(width);
					len = cont.length();
				} else {
					fcell.add(cont);
					cont = "";
				}
				nln++;
				if (nln > maxln) {
					maxln = nln;
				}
			}
			allCells.add(fcell);
		}
		//assemble lines
		for (int i = 0; i < maxln; i++) {
			StringBuffer buff = new StringBuffer();
			for (int j = 0; j < cells.length; j++) {
				String cln = "";
				ArrayList<String> cellText = allCells.get(j);
				if (cellText.size() > i) {
					cln = cellText.get(i);
				}
				cln = formats[j].formatCellLine(cln);
				buff.append(cln);
				if (j < cells.length - 1) {
					buff.append(colSep);
				}
			}
			allLines.add(buff.toString());
		}
	}

	public void addColumn(String colName) {
		headerFormat.add(new ColumnFormat(colName));
	}

	public void setColumnWidth(String cname, int ith, TextTable table) {
		//simple and stupid
		for (ColumnFormat cf : headerFormat) {
			if (cname.equals(cf.getName())) {
				cf.setWidth(ith);
				if (table != null) {
					calculateColWidths(table);
				}
			}
		}
	}

	public void cleanHeaders() {
		headerFormat.clear();
	}

	public void setShowHeaders(boolean b) {
		showHeaders = b;
	}

	public void addColumns(String[] cols) {
		for (String cl : cols) {
			addColumn(cl);
		}
	}

	public void setColumnSep(String sep) {
		colSep = sep;
	}
}
